/* 
	1、把template解析出来，引用了状态的给换上、绑定了事件的给挂上、用了v-model的给接通
	2、构建Vue构造函数，做好初始化步骤：取数据、取函数
	3、对data进行绑定
 */

	function isObject(data) {
		return data && typeof data === 'object'
	}
	class Vue {
		constructor(options) {
			// 初始化数据收集
			this.$options = options
			this.$el = options.el
			this.$data = options.data
			this.$methods = options.methods
			// 对data数据做代理，达到可通过实例直接访问到data中数据的效果
			proxy(this, this.$data)
			// 对数据添加监听
			observer(this.$data)
			// 执行编译函数，此时会对数据进行引用。
			new Compiler(this)
		}
	}
	
	function proxy(target, data) {
		Object.keys(data).forEach(key => {
			Object.defineProperty(target, key, {
				enumerable: true,
				configurable: true,
				get() {
					return data[key]
				},
				set(newVal) {
					if (newVal !== data[key]) data[key] = newVal
				}
			})
		})
	}
	
	function observer(data) {
		new Observer(data)
	}
	
	class Observer {
		constructor(data) {
			this.walk(data)
		}
	
		walk(data) {
			if (isObject(data)) {
				Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]))
			}
		}
	
		defineReactive(data, key, value) {
			let that = this
			this.walk(value)
			let dep = new Dep()
			Object.defineProperty(data, key, {
				get() {
					// track
					if (Dep.target) {
						dep.add(Dep.target)
					}
					return value
				},
				set(newVal) {
					// triger
					if (newVal !== value) {
						// set函数产生了闭包，value成为了自由变量
						value = newVal;
						// 这一步很关键，如果值被更新了，要对新的数据执行代理
						that.walk(newVal);
						// 也有事情要干， 执行
						dep.notify()
					}
				}
			})
		}
	}
	// 把Dep封装一个类，Dep实例的作用就是维护回调函数数组，并且有方法可以通知所有的观察者更新数据,给被观察者用起来比较方便
	class Dep {
		constructor() {
			this.watchers = new Set()
		}
		add(watcher) {
			if (watcher && watcher.update) {
				this.watchers.push(watcher)
			}
		}
	
		notify() {
			this.watchers.forEach(e => e.update())
		}
	}
	
	class Watcher {
		constructor(vm, key, cb) {
			// 类中存了什么属性和方法，其实都是给底下的函数用的,底下函数需要用到什么，就给存什么
			this.vm = vm
			this.key = key
			this.cb = cb
			Dep.target = this
			//TODO: 此处没有调用cb，而是直接取值，为什么？
			this.__old = vm[key]
			Dep.target = null
		}
	
		update() {
			//TODO: 这里的newVal是从实例中取得值，无需传值，和调用处解耦
			let newVal = this.vm[this.key]
			if (newVal !== this.__old) {
				this.cb(newVal)
			}
		}
	}
	
	class Compiler {
		constructor(vm) {
			this.$el = vm.$el
			this.vm = vm
			this.$methods = vm.methods
			this.compile(vm.$el)
		}
	
		compile(el) {
			let childNodes = el.childNodes
			Array.from(childNodes).forEach(node => {
				if (node.nodeType === 3) {
					this.compileText(node)
				}
	
				if (node.nodeType === 1) {
					this.compileElement(node)
				}
	
				if (node.childNodes && node.childNodes.length > 0) this.compile(node)
			})
		}
	
		compileText(node) {
			// 从dom中取出引用，把引用替换为数据。添加数据监听
			let reg = /\{\{(.+?)\}\}/;
			let value = node.textContent;
			if (reg.test(value)) {
				let key = RegExp.$1.trim();
				// 开始时，先赋值 key -> message 
				node.textContent = value.replace(reg, this.vm[key]);
				// 添加观察者
				new Watcher(this.vm, key, val => {
					// render 函数
					node.textContent = val;
				})
			}
		}
	
		compileElement(node) {
			if (node.attributes.length) {
				Array.from(node.attributes).forEach(attr => {
					let attrName = attr.name;
					if (attrName.startsWith('v-')) {
						// v- 匹配成功，可能是 v-on:, v-model
						attrName = attrName.indexOf(':') > -1 ? attrName.substr(5) : attrName.substr(2);
						let key = attr.value;
						// 
						this.update(node, key, attrName, this.vm[key])
					}
				})
			}
		}
	
		update(node, key, attrName, value) {
			if (attrName === "model") {
				node.value = value;
				new Watcher(this.vm, key, val => node.value = val);
				node.addEventListener('input', () => {
					this.vm[key] = node.value
				})
			} else if (attrName === 'click') {
				node.addEventListener(attrName, this.methods[key].bind(this.vm))
			}
		}
	}
	
	export default Vue
	
	/* 	
		vue2中实现响应式的这段核心代码，有几点我觉得处理的很漂亮：
		1.proxy、observer方法，都单拎出来了，很清晰。这些方法都是纯函数，和实例不耦合
		2.Dep类主要功能是维护一个观察者数组，并有方法可以一次性通知到所有观察者，所以和实例也不耦合
		3.Watch类，watch相当于是一个“观察”动作，给模版使用，用来观察数据。所以获取watch实例的时候，
		需要告诉watch类要观察哪个实例、观察的具体字段是哪个，回调函数是什么
	
		开发这部分功能之前，我觉得应该做一个代码设计，比如一共需要实现哪些功能，拿vue2举例，需要实现
		数据的代理、数据变化以后的代理、维护一个观察者列表、观察者和数据进行绑定，对于这些功能，应该先
		做个模块划分，然后屡一下模块之间的关系，最终成型
	 */